﻿namespace FxCopContrib
{
    using System.Collections.Generic;
    using Microsoft.FxCop.Sdk;
    using System;

    /// <summary>
    /// This rule checks for any calls to methods on ImmutableTypes that also return an immutable type as return value.
    /// 
    /// Calls to such methods which are then ignored are often indicators of a bug.
    /// </summary>
    /// <example>
    /// <code>
    ///     DateTime value = DateTime.UtcNow;
    ///     value.AddHours(1);
    /// </code>
    /// <code>
    ///     String value = "abcdef";
    ///     value.Trim();
    /// </code>
    /// </example>
    public class DoNotIgnoreResultOfImmutableMethods : RuleBase
    {
        /// <summary>
        /// Keep a list of all immutable Types. This list is taken from the same source as the Microsoft SecurityRules use.
        /// It can be extended the same ways. See: http://blogs.msdn.com/b/codeanalysis/archive/2006/04/04/568562.aspx 
        /// </summary>
        private static IList<string> immutableTypes;

        /// <summary>
        /// Construct the rule and supply the rulename to the base rule.
        /// </summary>
        public DoNotIgnoreResultOfImmutableMethods() : base("DoNotIgnoreResultOfImmutableMethods") { }

        /// <summary>
        /// Make sure teh list of immutable types is loaded
        /// </summary>
        public override void BeforeAnalysis()
        {
            immutableTypes = RuleHelper.RetreiveImmutableTypes();
        }

        /// <summary>
        /// Clear the list after use
        /// </summary>
        public override void AfterAnalysis()
        {
            immutableTypes = null;
        }

        /// <summary>
        /// Required override of the Check method, to ensure all members of all types are properly inspected.
        /// </summary>
        /// <param name="member"></param>
        /// <returns></returns>
        public override ProblemCollection Check(Member member)
        {
            Visit(member);
            return this.Problems;
        }

        /// <summary>
        /// We need to find all Pop nodes, these are a member of the ExpressionStatement class.
        /// After we find a Pop statement (which ignores the current value on the stack), we will see if the value added
        /// to the stack is an instance MethodCall on an ImmutableType.
        /// </summary>
        /// <param name="statement"></param>
        public override void VisitExpressionStatement(ExpressionStatement statement)
        {
            if (statement == null) { throw new ArgumentNullException("statement"); }

            if (statement.Expression.NodeType == NodeType.Pop)
            {
                VisitUnaryExpression(statement.Expression as UnaryExpression);
            }
            base.VisitExpressionStatement(statement);
        }

        /// <summary>
        /// When we've found the UnaryExpression we check it for a MethodCall on an ImmutableType
        /// </summary>
        /// <param name="unaryExpression"></param>
        public override void VisitUnaryExpression(UnaryExpression unaryExpression)
        {
            if (unaryExpression == null) { throw new ArgumentNullException("unaryExpression"); }

            if (unaryExpression.NodeType == NodeType.Pop)
            {
                MethodCall call = unaryExpression.Operand as MethodCall;
                if (call != null)
                {
                    MemberBinding binding = call.Callee as MemberBinding;
                    if (binding.BoundMember.DeclaringType != null
                        && immutableTypes.Contains(binding.BoundMember.DeclaringType.FullName))
                    {
                        Method method = binding.BoundMember as Method;

                        // If the method also returns an immutable Type we flag it as a problem.
                        if (immutableTypes.Contains(method.ReturnType.FullName))
                        {
                            this.Problems.Add(new Problem(GetResolution(), call));
                        }
                    }
                }
            }
        }
    }
}
